/**************************************************************************
 *	bluefire-os
 *	Version: 00.00.04
 *	Author: David Davidson
 *	Name: paging.c
 *	Created: Jan 23, 2012
 *	Purpose:
 *  Usage:
***************************************************************************/
#include <common_include.h>

// declared in assembly/start.asm
extern u32int var_system_memory_amount;
// defined in kernel.ld
extern u32int KERNEL_TOP;

// End of kernel virtual space
u32int *K_VIR_END;
// Free-frames stack is placed just above kernel memory
u32int *free_frames = (u32int *)&KERNEL_TOP;

// Accounting record of the Master page directory
u32int K_PDBR[1024];

/**************************************************************************
*	Free frame stack. Basically one word of memory is recored to represent
*	every physical group (page) of memory not used. Later on a more efficient
*	bitset algorithm will be used but for now things are kept simple as there
*	arn't to many kernel resources available for debugging.
**************************************************************************/
// ---------- Free frames stack operators ----------
u32int pop_frame() {
	u32int ret;
	u32int flags;

	disable_and_save_interrupts(flags);

	if (*free_frames != NULL) {
		ret = *free_frames;
		*free_frames = NULL;
		free_frames++;

		restore_interrupts(flags);
		return(ret);
	}

	// Out of memory
	restore_interrupts(flags);
	return NULL;
}

void push_frame(u32int p_addr) {
	u32int flags;

	disable_and_save_interrupts(flags);

	// Push the frame into free frames stack
	if ((u32int)free_frames > ((u32int)&KERNEL_TOP)) {
		free_frames--;
		*free_frames=p_addr;
	}

	restore_interrupts(flags);
}

/**************************************************************************
*	---------- Mapping operators ----------
*	Associates a Virtual address with a physical address
**************************************************************************/
// ---------- Initialization routines ----------
void init_free_frames() {
	u32int p_addr;

	// First physical 16MB are reserved for kernel, BIOS & DMA
	// so let's start with free memory area at 16MB
	// -> 0xC0015000 - 0xC0031000 : 0x(1000-8000)
	p_addr = P_ADDR_16MB;		//0x1000
	K_VIR_END = free_frames;	//(KERNEL_TOP, dynamic, can be looked up in build/bf_kernel.dis)
	while (p_addr < ADDR_TO_PAGE(var_system_memory_amount)) {
		*(K_VIR_END++) = p_addr++;
	}

	// Last frame is NULL => out of physical memory.
	// Kernel virtual address space ends here:
	*K_VIR_END=NULL;
}

// ---------- Actual map routine ----------
s32int map_page(u32int vir_addr, u32int phys_addr, u16int attribs) {
	// Perform a page mapping for the current address space
	u32int *PTE;
	u32int i;

	u32int flags;
	disable_and_save_interrupts(flags);

	// Round virtual & physical address to the page boundary
	vir_addr = PAGE_ALIGN(vir_addr);
	phys_addr = PAGE_ALIGN(phys_addr);

	// Get only valid attributes
	attribs &= (PAGE_SIZE-1);


	// If the page directory entry is NULL must be created
	if (*ADDR_TO_PDE(vir_addr) == NULL) {
		// Create a new page table
		PTE = (u32int *)(pop_frame() * PAGE_SIZE);
		if (PTE == NULL) {
			// Out of memory
			restore_interrupts(flags);
			return(FALSE);
		}

		// Set the PDE as present, user level, read-write
		*ADDR_TO_PDE(vir_addr) = (u32int)PTE | P_PRESENT | P_USER | P_WRITE;

		// Invalidate the self-mapping page
		invlpg((u32int)ADDR_TO_PTE(vir_addr));

		// NULL every PTE entry
		for (i=PAGE_DIR_ALIGN(vir_addr); i<PAGE_DIR_ALIGN_UP(vir_addr); i+=PAGE_SIZE) {
			*ADDR_TO_PTE(i) = NULL;
		}

		// Update master page directory
		if (vir_addr >= VIRTUAL_KERNEL_START) {
			K_PDBR[vir_addr/(PAGE_SIZE*1024)] = *ADDR_TO_PDE(vir_addr);
		}
	}

	// Store the physical address into the page table entry
	*ADDR_TO_PTE(vir_addr) = (u32int)phys_addr | attribs;

	// Invalidate the page in the TLB cache
	invlpg(vir_addr);

	restore_interrupts(flags);

	return(TRUE);
}

void delete_page(u32int addr) {
	// Unmap the page and destroy the physical frame where the address is mapped
	u32int temp;

	// Align address to the page boundary
	addr = PAGE_ALIGN(addr);

	if (*ADDR_TO_PDE(addr) == NULL) return;
	if (*ADDR_TO_PTE(addr) == NULL) return;

	u32int flags;
	disable_and_save_interrupts(flags);


	// Push the physical frame into the free frames stack
	if ((virtual_to_physical_address(addr) > PHYSICAL_DMA_MEMORY_END) && (virtual_to_physical_address(addr) < var_system_memory_amount))
		push_frame(virtual_to_physical_address(addr)/PAGE_SIZE);

	// Unmap the page
	*ADDR_TO_PTE(addr) = NULL;

	// Invalidate the page in the TLB cache
	invlpg(addr);

	// Check if it is possible to deallocate the frame
	// of the page table used to map the address
	// So let's examine all entries in the page table
	// where the address is mapped.
	for( temp = PAGE_DIR_ALIGN(addr); temp < PAGE_DIR_ALIGN_UP(addr); temp += PAGE_SIZE ) {
		if (*ADDR_TO_PTE(temp) != NULL){
			restore_interrupts(flags);
			return;
		}
	}

	// No PTEs found... deallocate the page table!
	push_frame(*ADDR_TO_PDE(addr)/PAGE_SIZE);
	*ADDR_TO_PDE(addr) = NULL;

	// Invalidate the self-mapping page
	invlpg((u32int)ADDR_TO_PTE(addr));

	// ...and update the master page directory!
	if (addr >= VIRTUAL_KERNEL_START)
		K_PDBR[addr/(PAGE_SIZE*1024)] = NULL;

	restore_interrupts(flags);
}

// ---------- Generic operators ----------
u32int virtual_to_physical_address(u32int vir_addr) {
	// Returns the physical address associated with the virtual address
	if (*ADDR_TO_PDE(vir_addr) == NULL) return(NULL);
	return ((*ADDR_TO_PTE(vir_addr) & -PAGE_SIZE) + (vir_addr % PAGE_SIZE));
}

/**************************************************************************
*	---------- Page fault handler ----------
*	This function is hardcoded in the IDT and called on a page fault exception.
*	returns - A error code in case of failure.
**************************************************************************/
s32int page_fault_handler(u32int err_code, u32int cr2) {
	u32int phys_addr;

	phys_addr = pop_frame() * PAGE_SIZE;

	// If out of memory return with a marked page fault panic
	if (phys_addr == NULL) 	{
		kset_color(LIGHT_RED);
		kprintf("\n\rPage fault handler panic: Out of memory!!!");
		kset_color(DEFAULT_COLOR);
		return TRUE;
	}

	// Get only valid err_code
	err_code &= (P_PRESENT | P_WRITE | P_USER);

	// Map page with correct attributes
	if (cr2 >= VIRTUAL_KERNEL_START) {
		if (!(map_page(cr2, phys_addr, P_PRESENT | P_WRITE))) {
			// Out of memory
			kset_color(LIGHT_RED);
			kprintf("\n\rPage fault handler panic: Out of memory!!!");
			kset_color(DEFAULT_COLOR);
			return TRUE;
		}
	} else {
		if (!(map_page(cr2, phys_addr, P_PRESENT | P_WRITE | P_USER))) {
			// Out of memory
			kset_color(LIGHT_RED);
			kprintf("\n\rPage fault handler panic: Out of memory!!!");
			kset_color(DEFAULT_COLOR);
			return TRUE;
		}
	}
	// Initialize the new page
	memset08((void *)PAGE_ALIGN(cr2), 0, PAGE_SIZE);
	return FALSE;
}

/**************************************************************************
*Sets up everything we need for paging
* On from start.asm paging looks like
* 0x00000000-0x003fffff -> 0x0000000000000000-0x00000000003fffff
* 0xc0000000-0xc03fffff -> 0x0000000000100000-0x00000000004fffff
* 0xfffff000-0xffffffff -> 0x0000000000001000-0x0000000000001fff
* 0xfff00000-0xfff00fff -> 0x0000000000002000-0x0000000000002fff
* 0xffc00000-0xffc00fff -> 0x0000000000003000-0x0000000000003fff
**************************************************************************/
void initialize_paging() {
	// TODO flush the TLB?
	u32int addr;


	// Initialize free frames stack
	init_free_frames();

	// remove 0x00000000-0x003fffff -> 0x0000000000000000-0x00000000003fffff
	// Every process assumes it has the first 3GB of memory to its self. Until now
	// lower memory was identity mapped so 0x1000(V) = 0x1000(P) while the page directory that resided in it
	// was self mapped and addressable as either 0x1000(V) or 0xFFFFF000(V) both mapping to 0x1000(P). Now Lower
	// memory will be unmapped and only the self mapped will work.
	// -> 0xFFFFF000 : 0x0
	*ADDR_TO_PDE(0) = NULL;

	// add 0xe0000000-0xe0ffffff -> 0x0000000000000000-0x0000000000ffffff
	// Map physical memory into the kernel address space
	// Map the physical addresses of the first 16 MB of memory to Virtual addresses 0xE0000000 to 0xE1000000
	for ( addr = VIRTUAL_ACTUAL_MEMORY_START; addr < VIRTUAL_ACTUAL_MEMORY_END; addr += PAGE_SIZE) {
		map_page(addr, addr-VIRTUAL_ACTUAL_MEMORY_START, P_PRESENT | P_WRITE);
	}

	// Initialize master page directory
	// The master page directory is a manually maintained record of the kernel's page directory
	// Processes will us it when they fork as a base of their page tables.
	for (addr = 0; addr < 1024; addr++) {
		K_PDBR[addr] = ((u32int *)VIRTUAL_PAGE_DIRECTORY_MAP)[addr];
	}

	reload_CR3();

	// Paging now looks like.
	// 0xc0000000-0xc03fffff -> 0x0000000000100000-0x00000000004fffff
	// 0xe0000000-0xe0ffffff -> 0x0000000000000000-0x0000000000ffffff
	// 0xfff00000-0xfff00fff -> 0x0000000000002000-0x0000000000002fff
	// 0xfff80000-0xfff83fff -> 0x0000000001000000-0x0000000001003fff
	// 0xfffff000-0xffffffff -> 0x0000000000001000-0x0000000000001fff
}

// ---------- Debug functions ----------
// Show all the dirty pages
void dump_dirty_pages() {
	u32int vir_addr;
	u32int display = 1;

	// Print all the dirty pages
	kprintf("\nDirty pages:\n");
	for (vir_addr = 0; vir_addr < VIRTUAL_PAGE_TABLE_MAP ; vir_addr += PAGE_SIZE) {
		if (*ADDR_TO_PDE(vir_addr) != NULL) {
			if ((*ADDR_TO_PTE(vir_addr) & P_DIRTY) == P_DIRTY) {
				if (!(++display % 24)){
					//if (getchar() == CTRL_C) { // no keyboard yet
					//	printf("\n\r");
					//	return;
					//}
					dbg_pause(2)
				}
				kprintf("\nvir_addr = %X\tpage_entry = %X", vir_addr, *(ADDR_TO_PTE(vir_addr)) );
			}
		}
	}
	kprintf("\n");
}

void dump_free_frames() {
	u32int *f = free_frames;
	u32int display = 1;

	kprintf("\nFree frames list: (KERNEL_TOP=%X)\n", (u32int)&KERNEL_TOP);
	for( ; ; ) {
		if (*f == NULL) {
			break;
		}
		if (!(++display % 24)) {
			//if (getchar() == CTRL_C) { // no keyboard yet
			//	printf("\n\r");
			//	return;
			//}
			dbg_pause(2);
		}
		kprintf("\nframe #%X &frame=%X", *f, (u32int)f);
		f++;
	}
	kprintf("\n");
}
